
#include <BOpenGL>
#include <BCuller>
#include <BRender>

#include <BGeometry>
#include "member_BGeometry.h"

using namespace BWE;

#define member					(*(member_BGeometry*)_ptr)
#define member_allocate()		_ptr = new member_BGeometry
#define member_release()		delete (member_BGeometry*)_ptr

BGeometry::BGeometry()
{
	member_allocate();
}
BGeometry::BGeometry(const BString& name)
{
	member_allocate();
	this->setName(name);
}
BGeometry::~BGeometry()
{
	member_release();
}

void BGeometry::setVertices(BArray<BVector>* vertices)
{
	member.vertices = vertices;
	this->dirty();
}
BArray<BVector>* BGeometry::vertices()
{
	return member.vertices.ptr();
}
const BArray<BVector>* BGeometry::vertices() const
{
	return member.vertices.ptr();
}

void BGeometry::setNormals(BArray<BVector>* normals)
{
	member.normals = normals;
}
BArray<BVector>* BGeometry::normals()
{
	return member.normals.ptr();
}
const BArray<BVector>* BGeometry::normals() const
{
	return member.normals.ptr();
}

void BGeometry::setTexCoords(BCoordArray* texCoords)
{
	member.coords = texCoords;
}
BCoordArray* BGeometry::texCoords()
{
	return member.coords;
}
const BCoordArray* BGeometry::texCoords() const
{
	return member.coords;
}

void BGeometry::setColors(BColorArray* colors)
{
	member.colors = colors;
}
BColorArray* BGeometry::colors()
{
	return member.colors.ptr();
}
const BColorArray* BGeometry::colors() const
{
	return member.colors.ptr();
}

void BGeometry::addPrimitive(BPrimitive* primitive)
{
	if (primitive && !member.primitives.contain(primitive))
	{
		member.primitives.append(primitive);
	}
}
void BGeometry::addPrimitive(BPrimitive::Mode mode, int first, int count)
{
	BPrimitive* primitive = new BPrimitive(mode, first, count);
	addPrimitive(primitive);
}

void BGeometry::removePrimitive(const BPrimitive* primitive)
{
	member.primitives.remove(primitive);
}
void BGeometry::clearPrimitives()
{
	member.primitives.clear();
}

int BGeometry::primitiveCount() const
{
	return member.primitives.size();
}
BPrimitive* BGeometry::primitive(int index)
{
	return member.primitives(index);
}
const BPrimitive* BGeometry::primitive(int index) const
{
	return member.primitives(index);
}

void BGeometry::clear()
{
	member.vertices = 0;
	member.normals = 0;
	member.colors = 0;
	member.coords->clear();
	member.primitives.clear();
	this->dirty();
}
bool BGeometry::empty() const
{
	return member.vertices == 0;
}
void BGeometry::smooth()
{
	BVectorArray* vertices = member.vertices;
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != vertices->size())
	{
		normals = new BVectorArray(vertices->size());
		this->setNormals(normals);
	}
	normals->fill(BVector());
	for (int i = 0; i < member.primitives.size(); i++)
	{
		BPrimitive* primitive = member.primitives[i];
		int first = primitive->first();
		int count = primitive->count();
		BIndexArray* indices = primitive->indices();
		if (primitive->mode() == BPrimitive::Triangles)
		{
			for (int i = 0; i < count; i += 3)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				unsigned int ic = (*indices)[first + i + 2];
				const BVector& va = (*vertices)[ia];
				const BVector& vb = (*vertices)[ib];
				const BVector& vc = (*vertices)[ic];
				BVector normal = (vb - va).cross(vc - vb);
				normal.normalize();
				(*normals)[ia] += normal;
				(*normals)[ib] += normal;
				(*normals)[ic] += normal;
			}
		}
		if (primitive->mode() == BPrimitive::Quads)
		{
			for (int i = 0; i < count; i += 4)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				unsigned int ic = (*indices)[first + i + 2];
				unsigned int id = (*indices)[first + i + 3];
				const BVector& va = (*vertices)[ia];
				const BVector& vb = (*vertices)[ib];
				const BVector& vc = (*vertices)[ic];
				BVector normal = (vb - va).cross(vc - vb);
				normal.normalize();
				(*normals)[ia] += normal;
				(*normals)[ib] += normal;
				(*normals)[ic] += normal;
				(*normals)[id] += normal;
			}
		}
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i].normalize();
	}
	this->setNormals(normals);
}
void BGeometry::reversal()
{
	for (int i = 0; i < member.primitives.size(); i++)
	{
		BPrimitive* primitive = member.primitives[i];
		int first = primitive->first();
		int count = primitive->count();
		BIndexArray* indices = primitive->indices();
		if (primitive->mode() == BPrimitive::Triangles)
		{
			for (int i = 0; i < count; i += 3)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				(*indices)[first + i] = ib;
				(*indices)[first + i + 1] = ia;
			}
		}
		if (primitive->mode() == BPrimitive::Quads)
		{
			for (int i = 0; i < count; i += 4)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				unsigned int ic = (*indices)[first + i + 2];
				unsigned int id = (*indices)[first + i + 3];
				(*indices)[first + i] = id;
				(*indices)[first + i + 1] = ic;
				(*indices)[first + i + 2] = ib;
				(*indices)[first + i + 3] = ia;
			}
		}
	}
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != this->vertices()->size())
	{
		smooth();
		return;
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i] = -(*normals)[i];
	}
}

void BGeometry::refresh()
{
	BSpace space;
	if (member.vertices && member.vertices->size())
	{
		space.reset(member.vertices->first());
		for (int i = 1; i < member.vertices->size(); i++)
		{
			space.expand((*member.vertices.ptr())[i]);
		}
	}
	this->setSpace(space);
}
void BGeometry::render(BRender& render)
{
	if(!member.vertices || member.vertices->empty() || member.primitives.empty())
		return;

	if (member.vertices)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, member.vertices->data());
	}

	if (member.normals)
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT, 0, member.normals->data());
	}

	if (member.colors)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_UNSIGNED_BYTE, 0, member.colors->data());
	}

	if (member.coords && member.coords->size() && this->texture())
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, member.coords->data());
	}

	for (int i = 0; i < member.primitives.size(); i++)
	{
		BPrimitive* primitive = member.primitives[i];
		GLenum mode = 0;
		switch (primitive->mode())
		{
		case BPrimitive::Points:		mode = GL_POINTS;			break;
		case BPrimitive::Lines:			mode = GL_LINES;			break;
		case BPrimitive::LineStrip:		mode = GL_LINE_STRIP;		break;
		case BPrimitive::LineLoop:		mode = GL_LINE_LOOP;		break;
		case BPrimitive::Triangles:		mode = GL_TRIANGLES;		break;
		case BPrimitive::TriangleStrip:	mode = GL_TRIANGLE_STRIP;	break;
		case BPrimitive::TriangleFan:	mode = GL_TRIANGLE_FAN;		break;
		case BPrimitive::Quads:			mode = GL_QUADS; 			break;
		case BPrimitive::QuadStrip:		mode = GL_QUAD_STRIP;		break;
		case BPrimitive::Polygon:		mode = GL_POLYGON;			break;
		default:
			continue;
		}
		BIndexArray* indices = primitive->indices();
		if (indices)
		{
			int count = bMin(primitive->count(), indices->size());
			glEnableClientState(GL_INDEX_ARRAY);
			glDrawElements(mode, count, GL_UNSIGNED_INT, indices->data());
			glDisableClientState(GL_INDEX_ARRAY);
		}
		else
		{
			int first = primitive->first();
			int count = primitive->count();
			glDrawArrays(mode, first, count);
		}
	}

	if (member.vertices)
	{
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	if (member.normals)
	{
		glDisableClientState(GL_NORMAL_ARRAY);
	}
	if (member.colors)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	if (member.coords && member.coords->size() && this->texture())
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
}


